 /*******************************************************************************
  * Copyright (c) 2003, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM - Initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.events;

 import org.eclipse.core.internal.resources.ResourceException;
 import org.eclipse.core.internal.resources.Workspace;
 import org.eclipse.core.internal.utils.Messages;
 import org.eclipse.core.internal.utils.Policy;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
 import org.eclipse.core.runtime.jobs.ISchedulingRule;
 import org.eclipse.core.runtime.jobs.Job;
 import org.osgi.framework.Bundle;

 /**
  * The job for performing workspace auto-builds, and pre- and post- autobuild
  * notification. This job is run whenever the workspace changes regardless
  * of whether autobuild is on or off.
  */
 class AutoBuildJob extends Job implements Preferences.IPropertyChangeListener {
     private boolean avoidBuild = false;
     private boolean buildNeeded = false;
     private boolean forceBuild = false;
     /**
      * Indicates that another thread tried to modify the workspace during
      * the autobuild. The autobuild should be immediately rescheduled
      * so that it will run as soon as the next workspace modification completes.
      */
     private boolean interrupted = false;
     private boolean isAutoBuilding = false;
     private long lastBuild = 0L;
     private Preferences preferences = ResourcesPlugin.getPlugin().getPluginPreferences();
     private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
 private Workspace workspace;

     AutoBuildJob(Workspace workspace) {
         super(Messages.events_building_0);
         setRule(workspace.getRoot());
         setPriority(BUILD);
         isAutoBuilding = workspace.isAutoBuilding();
         this.workspace = workspace;
         this.preferences.addPropertyChangeListener(this);
     }

     /**
      * Used to prevent auto-builds at the end of operations that contain
      * explicit builds
      */
     synchronized void avoidBuild() {
         avoidBuild = true;
     }

     public boolean belongsTo(Object family) {
         return family == ResourcesPlugin.FAMILY_AUTO_BUILD;
     }

     /**
      * Instructs the build job that a build is required. Ensure the build
      * job is scheduled to run.
      * @param needsBuild Whether a build is required, either due to
      * workspace change or other factor that invalidates the built state.
      */
     synchronized void build(boolean needsBuild) {
         buildNeeded |= needsBuild;
         long delay = computeScheduleDelay();
         int state = getState();
         if (Policy.DEBUG_BUILD_NEEDED)
             Policy.debug("Build requested, needsBuild: " + needsBuild + " state: " + state + " delay: " + delay); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 if (needsBuild && Policy.DEBUG_BUILD_NEEDED_STACK && state != Job.RUNNING)
             new RuntimeException ("Build Needed").printStackTrace(); //$NON-NLS-1$
 //don't mess with the interrupt flag if the job is still running
 if (state != Job.RUNNING)
             setInterrupted(false);
         switch (state) {
             case Job.SLEEPING :
                 wakeUp(delay);
                 break;
             case NONE :
                 setSystem(!isAutoBuilding);
                 schedule(delay);
                 break;
         }
     }

     /**
      * Computes the delay time that autobuild should be scheduled with. The
      * value will be in the range (MIN_BUILD_DELAY, MAX_BUILD_DELAY).
      */
     private long computeScheduleDelay() {
         return Math.max(Policy.MIN_BUILD_DELAY, Policy.MAX_BUILD_DELAY + lastBuild - System.currentTimeMillis());
     }

     /**
      * The autobuild job has been canceled. There are two flavours of
      * cancel, explicit user cancelation, and implicit interruption due to another
      * thread trying to modify the workspace. In the latter case, we must
      * make sure the build is immediately rescheduled if it was interrupted
      * by another thread, so that clients waiting to join autobuild will properly
      * continue waiting
      * @return a status with severity <code>CANCEL</code>
      */
     private synchronized IStatus canceled() {
         //regardless of the form of cancelation, the build state is not happy
 buildNeeded = true;
         //schedule a rebuild immediately if build was implicitly canceled
 if (interrupted) {
             if (Policy.DEBUG_BUILD_INTERRUPT)
                 System.out.println("Scheduling rebuild due to interruption"); //$NON-NLS-1$
 setInterrupted(false);
             schedule(computeScheduleDelay());
         }
         return Status.CANCEL_STATUS;
     }

     private void doBuild(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
         monitor = Policy.monitorFor(monitor);
         try {
             monitor.beginTask("", Policy.opWork); //$NON-NLS-1$
 final ISchedulingRule rule = workspace.getRuleFactory().buildRule();
             try {
                 workspace.prepareOperation(rule, monitor);
                 workspace.beginOperation(true);
                 final int trigger = IncrementalProjectBuilder.AUTO_BUILD;
                 workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.PRE_BUILD, trigger);
                 IStatus result = Status.OK_STATUS;
                 try {
                     if (shouldBuild())
                         result = workspace.getBuildManager().build(trigger, Policy.subMonitorFor(monitor, Policy.opWork));
                 } finally {
                     //always send POST_BUILD if there has been a PRE_BUILD
 workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.POST_BUILD, trigger);
                 }
                 if (!result.isOK())
                     throw new ResourceException(result);
                 buildNeeded = false;
             } finally {
                 //building may close the tree, but we are still inside an
 // operation so open it
 if (workspace.getElementTree().isImmutable())
                     workspace.newWorkingTree();
                 workspace.endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork));
             }
         } finally {
             monitor.done();
         }
     }
     
     /**
      * Forces an autobuild to occur, even if nothing has changed since the last
      * build. This is used to force a build after a clean.
      */
     public void forceBuild() {
         forceBuild = true;
     }

     /**
      * Another thread is attempting to modify the workspace. Flag the auto-build
      * as interrupted so that it will cancel and reschedule itself
      */
     synchronized void interrupt() {
         //if already interrupted, do nothing
 if (interrupted)
             return;
         switch (getState()) {
             case NONE :
                 return;
             case WAITING :
                 //put the job to sleep if it is waiting to run
 setInterrupted(!sleep());
                 break;
             case RUNNING :
                 //make sure autobuild doesn't interrupt itself
 if (Job.getJobManager().currentJob() == this)
                     return;
                 setInterrupted(true);
                 if (interrupted && Policy.DEBUG_BUILD_INTERRUPT) {
                     System.out.println("Autobuild was interrupted:"); //$NON-NLS-1$
 new Exception ().fillInStackTrace().printStackTrace();
                 }
                 break;
         }
         //clear the autobuild avoidance flag if we were interrupted
 if (interrupted)
             avoidBuild = false;
     }

     synchronized boolean isInterrupted() {
         if (interrupted)
             return true;
         //check if another job is blocked by the build job
 if (isBlocking())
             setInterrupted(true);
         return interrupted;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.Preferences.IPropertyChangeListener#propertyChange(org.eclipse.core.runtime.Preferences.PropertyChangeEvent)
      */
     public void propertyChange(PropertyChangeEvent event) {
         if (!event.getProperty().equals(ResourcesPlugin.PREF_AUTO_BUILDING))
             return;
         // get the new value of auto-build directly from the preferences
 boolean wasAutoBuilding = isAutoBuilding;
         isAutoBuilding = preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING);
         //force a build if autobuild has been turned on
 if (!forceBuild && !wasAutoBuilding && isAutoBuilding) {
             forceBuild = true;
             build(false);
         }
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor)
      */
     public IStatus run(IProgressMonitor monitor) {
         //synchronized in case build starts during checkCancel
 synchronized (this) {
             if (monitor.isCanceled()) {
                 return canceled();
             }
         }
         //if the system is shutting down, don't build
 if (systemBundle.getState() == Bundle.STOPPING)
             return Status.OK_STATUS;
         try {
             doBuild(monitor);
             lastBuild = System.currentTimeMillis();
             //if the build was successful then it should not be recorded as interrupted
 setInterrupted(false);
             return Status.OK_STATUS;
         } catch (OperationCanceledException e) {
             return canceled();
         } catch (CoreException sig) {
             return sig.getStatus();
         }
     }

     /**
      * Sets or clears the interrupted flag.
      */
     private synchronized void setInterrupted(boolean value) {
         interrupted = value;
     }

     /**
      * Returns true if a build is actually needed, and false otherwise.
      */
     private synchronized boolean shouldBuild() {
         try {
             //if auto-build is off then we never run
 if (!workspace.isAutoBuilding())
                 return false;
             //build if the workspace requires a build (description changes)
 if (forceBuild)
                 return true;
             if (avoidBuild)
                 return false;
             //return whether there have been any changes to the workspace tree.
 return buildNeeded;
         } finally {
             //regardless of the result, clear the build flags for next time
 forceBuild = avoidBuild = buildNeeded = false;
         }
     }
 }

